﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MadLibrary.Collections
{
    /// <summary>
    /// Provides generic enumerable collection.
    /// </summary>
    /// <typeparam name="T">Specifies reference generic type which must have a default (parameter less) constructor.</typeparam>
    public class Collection<T> : IEnumerable<T> where T : class, new()
    {
        /// <summary>
        /// Holds data of current collection.
        /// </summary>
        protected T[] data = new T[0];

        /// <summary>
        /// Gets count of elements in collection.
        /// </summary>
        public int Count { get { return data.Length; } }

        /// <summary>
        /// Creates new empty collection.
        /// </summary>
        public Collection()
        {
        }

        /// <summary>
        /// Creates new collection with specified items.
        /// </summary>
        /// <param name="data">Specifies items which have to be added into new collection.</param>
        public Collection(params T[] data)
            : this()
        {
            this.data = data;
        }

        /// <summary>
        /// Creates new collection with specified items.
        /// </summary>
        /// <param name="data">Specifies items which have to be added into new collection.</param>
        public Collection(IEnumerable<T> data)
            : this(data.ToArray())
        {
        }

        /// <summary>
        /// Gets or sets item at the specified position.
        /// </summary>
        /// <param name="i">Specifies index of item.</param>
        /// <returns>Returns the element on the specified position.</returns>
        public T this[int i]
        {
            get
            {
                return this.data[i];
            }
            set
            {
                this.data[i] = value;
            }
        }

        /// <summary>
        /// Adds the specfied item to collection.
        /// </summary>
        /// <param name="obj">The item to add to the collection.</param>
        public void Add(T obj)
        {
            int l = this.data.Length;
            T[] n = new T[l + 1];
            this.data.CopyTo(n, 0);
            n[l] = obj;
            this.data = n;
        }

        /// <summary>
        /// Adds multiple object to the collection
        /// </summary>
        /// <param name="objs">Specifies an array of elements which has to be added.</param>
        public void AddRange(params T[] objs)
        {
            foreach(T o in objs)
            {
                this.Add(o);
            }
        }

        /// <summary>
        /// Adds multiple object to the collection
        /// </summary>
        /// <param name="objs">Specifies a collection of elements which has to be added.</param>
        public void AddRange(IEnumerable<T> objs)
        {
            foreach(T o in objs)
            {
                this.Add(o);
            }
        }

        /// <summary>
        /// Inserts an item at the specified position in the collection.
        /// </summary>
        /// <param name="position">The position in which to insert the given item.</param>
        /// <param name="item">The item to insert at the specified collection.</param>
        public void InsertAt(int position, T item)
        {
            if(position < 0 || position > this.Count - 1)
                throw new IndexOutOfRangeException("Incorrect position passed for insert operation: Index was outside the array's size.");
            else if(position == 0)
            {
                T[] newarr = new T[this.data.Length + 1];
                newarr[0] = item;
                for(int i = 0; i < this.data.Length; i++)
                    newarr[i + 1] = this.data[i];
                this.data = newarr;
            }
            else if(position == this.data.Length - 1)
            {
                T[] newarr = new T[this.data.Length + 1];
                for(int i = 0; i < this.data.Length; i++)
                    newarr[i] = this.data[i];
                newarr[this.data.Length] = item;
                this.data = newarr;
            }
            else
            {
                T[] newarr = new T[this.data.Length + 1];
                for(int i = 0; i < position; i++)
                    newarr[i] = this.data[i];
                newarr[position] = item;
                for(int a = position + 1; a < this.data.Length; a++)
                    newarr[a] = this.data[a - 1];
            }
        }

        /// <summary>
        /// Gets indexes of array elements equals to the given value.
        /// </summary>
        /// <param name="obj">The value to be searched in the collection..</param>
        /// <returns>A System.Int32[] containing the positions of the value in the collection.</returns>
        public int[] IndexesOf(T obj)
        {
            int[] ret = new int[this.data.Count(a => a == obj)];
            if(ret.Length == 0)
                return ret;
            int lastIdx = -1;
            for(int i = 0; i < ret.Length; i++)
            {
                lastIdx = Array.IndexOf(ret, obj, lastIdx + 1);
                ret[i] = lastIdx;
            }
            return ret;
        }

        /// <summary>
        /// Gets the position of the first occurrence of the given value
        /// </summary>
        /// <param name="obj">The value to search within the collection.</param>
        /// <returns>The position of the first occurrence of the given value.</returns>
        public int FirstIndexOf(T obj)
        {
            int i = 0;
            foreach(T o in this.data)
            {
                if(obj == o)
                {
                    return i;
                }
                i++;
            }
            return -1;
        }

        /// <summary>
        /// Gets index of last occurence in collection.
        /// </summary>
        /// <param name="obj">Specifies item whichs last occurence is searched.</param>
        /// <returns>Last occurence of item specified.</returns>
        public int LastIndexOf(T obj)
        {
            int i = 0;
            int found = -1;
            foreach(T o in this.data)
            {
                if(o == obj)
                    found = i;
                i++;
            }
            return found;
        }

        /// <summary>
        /// Removes item specified.
        /// </summary>
        /// <param name="obj">Specifies item which has to be removed in collection.</param>
        /// <returns>Position of removed item.</returns>
        public int Remove(T obj)
        {
            int removed = -1;
            for(int i = 0; i < data.Length; i++)
            {
                if(obj != data[i])
                    continue;

                removed = i;
                break;
            }
            var trmv = new List<T>(this.data);
            trmv.RemoveAt(removed);
            this.data = trmv.ToArray();
            return removed;
        }

        /// <summary>
        /// Removes item on specified position.
        /// </summary>
        /// <param name="pos">Specifies position of item which has to be removed.</param>
        public void RemoveAt(int pos)
        {
            var rm = new List<T>(this.data);
            rm.RemoveAt(pos);
            this.data = rm.ToArray();
        }

        /// <summary>
        /// Determines whether this collection contains the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>
        ///   <c>true</c> if this collection contains the specified item; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(T item)
        {
            foreach(T t in this.data)
            {
                if(t == item)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Clears collection.
        /// </summary>
        public void Clear()
        {
            this.data = new T[0];
        }

        /// <summary>
        /// Gets array of items from current collection.
        /// </summary>
        /// <returns>Array of items of current collection.</returns>
        public T[] ToArray()
        {
            return this.data;
        }

        /// <summary>
        /// Gets list from items of current collection.
        /// </summary>
        /// <returns>List of items of current collection.</returns>
        public List<T> ToList()
        {
            return this.data.ToList();
        }

        /// <summary>
        /// Clones current collection.
        /// </summary>
        /// <returns>Copy of current collection.</returns>
        public Collection<T> Clone()
        {
            return new Collection<T>(this.data);
        }

        /// <summary>
        /// Gets generic collection of items.
        /// </summary>
        /// <returns>Generic collection with items of current collection.</returns>
        public Collection<T> ToCollection()
        {
            return this.Clone();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return new CollectionEnumerator<T>(this);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new CollectionEnumerator<T>(this);
        }
    }

    /// <summary>
    /// Serves as enumerator for Collection class.
    /// </summary>
    /// <typeparam name="T">The type.</typeparam>
    internal class CollectionEnumerator<T> : IEnumerator<T> where T : class, new()
    {
        private Collection<T> datac = new Collection<T>();

        public CollectionEnumerator(Collection<T> data)
        {
            this.datac = data;
        }

        private int pos = -1;

        T IEnumerator<T>.Current
        {
            get { return this.datac[this.pos]; }
        }

        void IDisposable.Dispose()
        {
        }

        object System.Collections.IEnumerator.Current
        {
            get { return this.datac[this.pos]; }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            if(pos < this.datac.Count - 1 && this.datac.Count != 0)
            {
                this.pos++;
                return true;
            }
            else return false;
        }

        void System.Collections.IEnumerator.Reset()
        {
            this.pos = -1;
        }
    }
}